/*
 * Session.h
 *
 *  Created on: 2014. 6. 25.
 *      Author: jjaehuny
 */

#ifndef FRAMEWORK_NETWORK_SESSION_H_
#define FRAMEWORK_NETWORK_SESSION_H_

#include <memory>
#include <functional>
#include <list>
#include <string>
#include <mutex>

#include <boost/asio.hpp>

#include <glog/logging.h>
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>

#include "../Memory/Lock_Function_new_Pool.h"
#include "../Macro.h"

#include "Packet.h"

namespace Framework { namespace Network {

class Session;
std::shared_ptr<::Framework::Network::Session> create_session_multithread();

} /*Network*/ } /*Framework*/



namespace Framework { namespace Network {

class Session : public std::enable_shared_from_this<Session>
{
public:
	class Observer
	{
	public:
		enum class E_EVENT {
			OPEN,
			CLOSE,
			MESSAGE,
		};

	public:
		Observer() {}
		virtual ~Observer() {}

		virtual void Initialize() { LOG(INFO) << __PRETTY_FUNCTION__ ; }
		virtual void Finalize() { LOG(INFO) << __PRETTY_FUNCTION__ ; }

	public:
		virtual void OnEvent(E_EVENT event) {}
		virtual void OnConnectFail(boost::system::error_code& ec) {}

		virtual int get_head_length() { return 4; }
		virtual int get_body_length(char * pBuffer) {
			short size = static_cast<short>(* pBuffer);
			return size;
		}
	};

public:
	Session() noexcept;
	virtual ~Session();

	Session(const Session & othre) = delete;
	Session & operator= (const Session & othre) = delete;

	/// call from pool
	void Initialize();
	void Finalize();

	std::shared_ptr<Session> CAPTURE();


	/// call from kernel
	void OnStart(boost::asio::ip::tcp::socket socket);


	/// call from logic
	void Connect(std::string & port);
	void Close();

	void set_observer(std::shared_ptr<Observer> observer) {
		LOG(INFO) << __PRETTY_FUNCTION__;
		observer_ = observer;
	}

	template<class MSG>
	void Send(MSG & msg);

	void Send(std::shared_ptr<Packet> packet) {
		bool isEmpty = false;
		{
			std::lock_guard<std::mutex> lock(send_mutex_);
			if (true == clsSendPacket_deque_.empty())
				isEmpty = true;

			clsSendPacket_deque_.push_back(packet->CAPTURE());
		}

		if (true == isEmpty)
			do_write();
	}

private:
	void do_read();
	void do_read_header();
	void do_read_body();
	void do_write();

public:

	boost::asio::ip::tcp::socket socket_;
	boost::asio::ip::tcp::resolver resolver_;

	std::shared_ptr<Packet> packet_;

	Memory::Lock_Function_new_Pool<Packet> clsReadPacketPool_;
	Memory::Lock_Function_new_Pool<Packet> clsSendPacketPool_;

	std::deque<std::shared_ptr<Packet> > clsReadPacket_deque_;
	std::deque<std::shared_ptr<Packet> > clsSendPacket_deque_;

	std::mutex send_mutex_;

	GETVAR(int, index_);
	std::shared_ptr<Observer> observer_;
};

template<class MSG>
void
Session::Send(MSG & msg)
{
	LOG(INFO) << __PRETTY_FUNCTION__;

	std::shared_ptr<Packet> packet = clsSendPacketPool_.NEW();

	int head_length = observer_->get_head_length();
	packet->set_head_length_(head_length);

	int bufSize = msg.ByteSize();

	// 버퍼에 직렬화
	google::protobuf::io::ArrayOutputStream os(packet->BodyBuffer(), bufSize);
	msg.SerializeToZeroCopyStream(&os);

	packet->set_send_length(bufSize + head_length);

	* packet->Buffer() = static_cast<short>(bufSize);
	* (packet->Buffer() + sizeof(short)) = static_cast<short>(msg.command());

	bool isEmpty = false;
	{
		std::lock_guard<std::mutex> lock(send_mutex_);
		if (true == clsSendPacket_deque_.empty())
			isEmpty = true;

		clsSendPacket_deque_.push_back(packet->CAPTURE());
	}

	if (true == isEmpty)
		do_write();
}


} /*Network*/ } /*Framework*/

#endif /* FRAMEWORK_NETWORK_SESSION_H_ */

